#!/bin/bash
#Parameters
#	$1 - action
#	$2 - productname

case "$1" in
	"parse" | "aggregate" ) 
		~/bin/analyzeGC.sh $0 Action=$1
		;;
		
	"count" )
		~/bin/analyzeGC.sh $0 Action=$1
		#zip blametargets${2}.zip blametargets*.csv
		#rm blametargets*.csv
		#rcp blametargets${2}.zip vhnguyen@brenta.disi.unitn.it:~
		;;
		
	"correctname" )
		~/bin/analyzeGC.sh $0 Action=$1
		;;
		
	"validate" | "movefile" )
		~/bin/rundesl.sh $0 Action=$1
		;;
esac

exit
#end
program BlameXml;

reference "core.desl";
//reference "chrome.desl";

pattern main(AnalysisOutput = ".", BlameOutput = ".", Version="1.0", Name="gc10", Action = "parse", RevisionMap = "revisionmap.csv") {
	match ConfigureLog arguments LogFile = "blamexml.log";
	//log "Blame folder: %s", BlameOutput;
	
	if (Action == "parse")
		match ProcessBlameFolder arguments Folder = new Folder(BlameOutput), 
			Version = Version;
	else if (Action == "count") 
		match CountTargets arguments Folder = new Folder(BlameOutput);
	else if (Action == "validate")
		match Validate;
	else if (Action == "correctname") {
		var scriptFile = String.format("%s/blame-%s.sh", {AnalysisOutput, Name});
		log "Process script file: %s", scriptFile;
		match CorrectBlameFile options minOccur = 0, maxOccur=@INFINITIVE, 
			input = new CharSequenceStream(scriptFile);		
	}
	else if (Action == "movefile") {
		//var scriptFile = new OutFile("move.sh");
		//scriptFile.writeln("#!/bin/bash");
		
		match MoveFile options maxOccur = @INFINITIVE,
			input = new CharSequenceStream("blamexml.log1");
		
		//scriptFile.close();
	}
	else if (Action == "aggregate")
		match Aggregate;
}

pattern MoveFile:DataPart(@"(/.+?/blame/).+? -> (.+?)$") {
	var xml = new File(this[2]);
	var csv = new File(this[2] + ".csv");
	
	if (xml.exists()) {
		log "%s -> %s: %s", this[2], this[1], xml.renameTo(new File(this[1] + this[2]));
		//scriptFile.writeln("mv %s %s", {this[2], this[1] + this[2]});
		if (csv.exists())
			csv.renameTo(new File(this[1] + this[2] + ".csv"));
			//scriptFile.writeln("mv %s %s", {this[2] + ".csv", this[1] + this[2] + ".csv"});
	}
}

pattern CorrectBlameFile:DataPart(@'"Annotate folder: (.+?) to (.+?)."') {
	var org = this[1].replace("/./", "/");
	var orgDir = new File(org);
	var orgFile = new File(this[2]);
	var orgFileCsv = new File(this[2] + ".csv");
	
	var newHash = Integer.toHexString(org.hashCode());
	
	var newName = String.format("%s-%s.xml", {orgDir.getName(), newHash});
	log "%s -> %s", this[2], newName;
	
	if (orgFile.exists()) {
		orgFile.renameTo(new File(newName));
		if (orgFileCsv.exists())
			orgFileCsv.renameTo(new File(newName + ".csv"));
	}  
} 

pattern Validate() {
	var sfile, bfile, sFileName, bFileName;
	var source = new HashMap();
	var blame = new HashMap();
	var script = new OutFile("remove.sh");
	script.writeln("#!/bin/bash");

	
	for(var ver = 1; ver <= 12; ver=ver+1) {
		log "Checking for version %d.0", ver;
		var blamefolder = String.format("gc%d.0/blame", {ver});
		sFileName = String.format("sourcefile-gc%d0.csv", {ver});
		bFileName = String.format("blametargets-gc%d0.csv", {ver});
		log "Compare %s vs. %s", sFileName, bFileName;
		
		// load blame file
		blame.clear();
		match pattern LoadBlameFile:DataPart(@"(.+?), (\d+)$") {
			blame.put(this[1], Integer.parseInt(this[2]));
		} options minOccur=0, maxOccur=@INFINITIVE, input = new CharSequenceStream(bFileName);
		
		// load source file
		source.clear();
		match pattern LoadSourceFile:DataPart(@"(.+?), (\d+), .+?$") {
			source.put(this[1], Integer.parseInt(this[2]));
		} options minOccur=0, maxOccur=@INFINITIVE, input = new CharSequenceStream(sFileName);
		
		// compare source vs. blame
		foreach(var key in source.keySet()) {
			if (blame.get(key) != source.get(key)) {
				log error "Inconsitency detected in %s (blame=%d, source=%d)", key, blame.get(key)??0, source.get(key)??0;
				if (blame.containsKey(key)) {
					script.writeln("rm %s/%s", {blamefolder, key});
					script.writeln("rm %s/%s.csv", {blamefolder, key});
				}
			}
		}
		
		// compare blame vs. source
		foreach(var key in blame.keySet()) {
			if (!source.containsKey(key) && blame.get(key) > 0) {
				log error "Source file not found %s (blame=%d, source=%d)", key, blame.get(key)??0, source.get(key)??0;
				script.writeln("rm %s/%s", {blamefolder, key});
			}
		}
	}
} 

pattern CountTargets(Folder = null) {
	var files = Folder.scanFiles(@"\.xml$", false);
	var targets = new HashMap();
	
	foreach(var file in files)
		match CountTargetFromBlameFile arguments File = file;
	
	var outfile = new CsvFile("blametargets-" + Name + ".csv");
	outfile.writeln("BlameFile, TargetCounts");
	
	foreach(var f in targets.keySet())
		outfile.writeCsvLine({f.getName(), targets.get(f)});
	
	outfile.close();
	log "Completed";
}

pattern CountTargetFromBlameFile(File = null) {
	log "Blame file %s", File.getName();
	var ip = new CharSequenceStream(File.getAbsolutePath(), 4096);
	match CountTarget options minOccur = 0, maxOccur = @INFINITIVE, input = ip;
	ip.close();
}

pattern CountTarget() {
	locate @'path="(.+?\.(c|cc|cpp|cxx|h|hh|hpp|hxx))">' using MatchText();
	locate "</target>" for end using MatchText();
	targets.put(File, (targets.get(File)??0) + 1);
}

pattern ProcessBlameFolder(Version = "1.0") {
	// process folder
	var files = Folder.scanFiles(@"\.xml$", false);

	foreach(var file in files)
		match BlameFile arguments File = file;
	
	log "Completed";
}

pattern BlameFile(File = null) {
	var outfile = File.getAbsolutePath() + ".csv";
	
	if (DeslUtil.fileExists(outfile)) 
		log "Skipped file: %s", File.toString();
	else {
		var lineCount = new HashMap();
		log "Process annotated file: %s", File.toString();
		
		var ip = new CharSequenceStream(File.getAbsolutePath(), 4096);
		var targets = match BlameTarget options minOccur=0, maxOccur = @INFINITIVE, input = ip;
		
		var out = new CsvFile(outfile);
		out.writeln("Revision, LineCount");
		foreach(var rev in lineCount.keySet()) 
			out.writeCsvLine({rev, lineCount.get(rev)});
		out.close();
		ip.close();
		log "%d entri(es) found in %s", targets.getOccurrenceIndex(), File.toString();
	}
}

pattern BlameTarget() {
	locate @'path="(.+?\.(c|cc|cpp|cxx|h|hh|hpp|hxx))">' using MatchText();
	locate "</target>" for end using MatchText();
	
	log "Target: %s", this.begin[1];
	match BlameEntry options minOccur = 0, maxOccur = @INFINITIVE;
}

pattern BlameEntry() {
	locate "<entry" for begin using MatchText();
	locate "</entry>" for end using MatchText();
	
	var rev = match EntryRevision options maxOccur = @INFINITIVE;
	if (rev.getOccurrenceIndex() > 0)
		log "More revisions for single entry detected.";
}

pattern EntryRevision:DataPart(@'revision="(\d+)"') {
	var key = this[1];
	lineCount.put(key, (lineCount.get(key) ?? 0) + 1);
}

pattern Aggregate() {
	var revMap = new HashMap();
	log "Loading revision map...%s", RevisionMap;
	// load revision map, if available
	if (DeslUtil.fileExists(RevisionMap)) 
		match pattern RevisionMapEntry:DataPart(@"(\d+), (.+?)$") {
			revMap.put(this[1], this[2]);
		} options minOccur=0, maxOccur=@INFINITIVE, input = new CharSequenceStream(RevisionMap), closeInputOnFinish=1;
	
	// scan for revision files
	log "Aggregate line count for version: %s", Version;
	var blameFolder = new Folder(BlameOutput);
	var files = blameFolder.scanFiles(@"\.csv$", false);
	
	// aggregate files
	var lineCount = new HashMap();
	foreach(var f in files)
		match pattern AggregateFile:DataPart(@"(\d+), (\d+)$") {
			if (!revMap.containsKey(this[1]))
				revMap.put(this[1], Version);
			var ver = revMap.get(this[1]);
			lineCount.put(ver, (lineCount.get(ver)??0) + this[2] );
		} options minOccur=0, maxOccur=@INFINITIVE, input=new CharSequenceStream(f.getAbsolutePath()), closeInputOnFinish=1;
	
	// saving
	var outFileName = String.format("linecount-%s.csv", {Name});
	log "Write line count to output...%s", outFileName;
	var outfile = new CsvFile(outFileName);
	outfile.writeln("Version, LineCount");
	foreach(var key in lineCount.keySet()) {
		outfile.writeCsvLine({key, lineCount.get(key)});
	}
	outfile.close();
	
	// saving revision map
	var revFile = new CsvFile(RevisionMap);
	log "Update revision map...";
	revFile.writeln("Revision, Version");
	foreach(var key in revMap.keySet()) 
		revFile.writeCsvLine({key, revMap.get(key)});
	revFile.close();
	
	log "Completed.";
}

